package ecprac.drivers.def;

import java.util.Random;

import ecprac.ea.abstracts.AbstractEA;
import ecprac.ea.abstracts.DriversUtils;
import ecprac.ea.abstracts.AbstractRace.DefaultTracks;
import ecprac.torcs.controller.Driver;

public class DefaultEA extends AbstractEA{

	private static final long serialVersionUID = 1L;

	Random r = new Random(12345); 

	static Cluster[] clusters;

	// The population 
	DriverChromosome[] population = new DriverChromosome[10];

	// The fitness values
	int[] fitness = new int[population.length];

	// The number of evaluations 
	int evals;

	public Class<? extends Driver> getDriverClass(){
		return DefaultDriver.class;
	}

	public void run() {
		// start a completely new run
		initializeClusters();
		run(false);
	}

	public void run(boolean continue_from_checkpoint) {

		/*
		 * 
		 * If we are not continuing from a previously
		 * terminated run, we need to initialize the population
		 * 
		 */
		if(!continue_from_checkpoint){
			//  initialization of the genomes
			initialize();
			evaluatePopulation();

		}


		// the evolutionary loop
		while (evals < 5000) {

			// find the best genome
			int best = 1;
			for(int i=0; i<population.length; i++){
				if (fitness[i] == 1) {
					best = i;
				}
			}

			// replace the worst 50% from the population with 
			// a mutated version of the best genome
			for(int i=0; i<population.length; i++){
				if(fitness[i] > population.length*0.5 ){
					population[i] = mutate(population[best]);
				}
			}
			population[0] = biasedRecombination(null);
			resetFitness();

			// Re-evaluate the population on a different track
			evaluatePopulation();

			// create a checkpoint 
			// this allows you to continue this run later
			DriversUtils.createCheckpoint(this);

			System.out.println("Evaluations: " + evals);

		}

		// clear the checkpoint
		DriversUtils.clearCheckpoint();

	}

	private void resetFitness() {
		for (int p = 0; p < population.length; p++) {
			for (int c = 0; c < clusters.length; c++) {
				population[p].fitness[c] = 0;
			}
		}
		
	}

	public void evaluatePopulation(){

		// Create a new Race object with a random track
		// from the set of default tracks
		DefaultRace race = new DefaultRace();
		race.setTrack( DefaultTracks.getTrack(r.nextInt(4)));
		race.laps = 2;
		// Run the race, fitness = rank
		// The GUI is set to true, for speedup set withGUI to false
		fitness = race.runRace(population, true);

		// Increment the number of evaluations
		evals+=population.length;

		// Save the best genome from the population
		for(int i=0; i<population.length; i++){
			if(fitness[i] == 1){
				DriversUtils.storeGenome(population[i]);
			}
		}	
	}

	public void initialize() {

		// Create an array of DefaultDriverGenome objects and
		// assign a random number [0 1] to each gene

		for (int i = 0; i < population.length; i++) {

			DriverChromosome chromosome = new DriverChromosome();

			for (int c = 0; c < chromosome.genes.length; c++) {
				chromosome.genes[c].brakingPoint = 4;
				chromosome.genes[c].accelerationPoint = 0;
				chromosome.genes[c].steeringWeights = new double[3];
				chromosome.genes[c].cornerSpeed = r.nextDouble()*100+50;
//				for (int j = 0; j < chromosome.genes[c].steeringWeights.length; j++) {
//					chromosome.genes[c].steeringWeights[j] = 1;
//				}
				chromosome.genes[c].steeringWeights[0] = 0;
				chromosome.genes[c].steeringWeights[1] = 0;
				chromosome.genes[c].steeringWeights[2] = -0.2;
				chromosome.genes[c].steeringStrength = 15;
			}
			population[i] = chromosome;
			fitness[i] = 0;
		}

	}

	public void initializeClusters() {
		clusters = new Cluster[4];
		clusters[0] = new Cluster(245.476, 393.172);
		clusters[1] = new Cluster(93.825, 83.791);
		clusters[2] = new Cluster(178.733, 138.027);
		clusters[3] = new Cluster(133.480, 122.430);
	}

	public DriverChromosome mutate(DriverChromosome genome) {

		DriverChromosome genome2 = new DriverChromosome();

		// Add some Gaussian noise
		for (int c = 0; c < genome.genes.length; c++) {
			genome2.genes[c].brakingPoint = genome.genes[c].brakingPoint + (1 * r.nextGaussian());
			genome2.genes[c].accelerationPoint = genome.genes[c].accelerationPoint + (1 * r.nextGaussian());
			genome2.genes[c].cornerSpeed = genome.genes[c].cornerSpeed + ((r.nextDouble()-0.1)*4);
			genome2.genes[c].steeringStrength = genome.genes[c].steeringStrength + (r.nextGaussian()*4);
			genome2.genes[c].steeringWeights[0] = genome.genes[c].steeringWeights[0] + (1 * r.nextGaussian());
			genome2.genes[c].steeringWeights[1] = genome.genes[c].steeringWeights[1] + (1 * r.nextGaussian());
			genome2.genes[c].steeringWeights[2] = genome.genes[c].steeringWeights[2] + (1 * r.nextGaussian());
			System.out.println("Genome: " +genome2.genes[c].steeringWeights[1]);
		}
		return genome2;
	}
	
	public DriverChromosome biasedRecombination(DriverChromosome genome) {
		DriverChromosome genome2 = new DriverChromosome();
		double[] mostFit = new double[clusters.length];
		for (int mF = 0; mF < mostFit.length; mF++) {
			mostFit[mF] = Double.MAX_VALUE;
		}
		for (int d = 0; d < population.length; d++) {
			for (int c = 0; c < clusters.length; c++) {
				if (population[d].fitness[c] < mostFit[c] && population[d].fitness[c] > 0) {
					System.out.println("Fitness: " +population[d].fitness[c]);
					mostFit[c] = population[d].fitness[c];
					genome2.genes[c] = population[d].genes[c];
				}
			}
		}
		return genome2;
	}


	public static void main(String[] args) {

		/*
		 * 
		 * Start without arguments to run the EA
		 * Start with -continue to continue a previous run
		 * Start with -show to show the best found
		 * Start with -show-race to show a race with 10 copies of the best found
		 * Start with -human to race against the best found
		 * 
		 */
		DefaultEA ea = new DefaultEA();
		DriversUtils.registerMemory(ea.getDriverClass());
		ea.initializeClusters();

		if(args.length > 0 && args[0].equals("-show")){
			new DefaultRace().showBest();
		} else if(args.length > 0 && args[0].equals("-show-race")){
			new DefaultRace().showBestRace();
		} else if(args.length > 0 && args[0].equals("-human")){
			new DefaultRace().raceBest();
		} else if(args.length > 0 && args[0].equals("-continue")){
			if(DriversUtils.hasCheckpoint()){
				DriversUtils.loadCheckpoint().run(true);
			} else {
				ea.run();
			}
		} else {
			ea.run();
		}
	}

}
